package com.rede.didiok.user.modulelist.rank.service.impl;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rede.didiok.common.constant.conf.MessageConf;
import com.rede.didiok.common.constant.conf.SQLConf;
import com.rede.didiok.common.constant.conf.SysConf;
import com.rede.didiok.common.dto.rank.RankCategoryDto;
import com.rede.didiok.common.enums.StatusEnum;
import com.rede.didiok.common.utils.RedisUtil;
import com.rede.didiok.common.utils.ResultUtil;
import com.rede.didiok.common.utils.StringUtils;
import com.rede.didiok.user.modulelist.rank.dao.RankCategoryDao;
import com.rede.didiok.user.modulelist.rank.entity.RankCategoryEntity;
import com.rede.didiok.user.modulelist.rank.entity.RankEntity;
import com.rede.didiok.user.modulelist.rank.service.RankCategoryService;
import com.rede.didiok.user.modulelist.rank.service.RankService;
import com.rede.didiok.user.modulelist.rank.vo.Catelog2Vo;


@Service("rankCategoryService")
public class RankCategoryServiceImpl extends ServiceImpl<RankCategoryDao, RankCategoryEntity> implements RankCategoryService {

    @Autowired
    private RankService rankService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /********************* 提供给 admin 的方法 region ***********************/


    @Override
    public IPage<RankCategoryEntity> getPageList(RankCategoryDto rankCategoryDto) {
        QueryWrapper<RankCategoryEntity> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotEmpty(rankCategoryDto.getKeyword()) && !StringUtils.isEmpty(rankCategoryDto.getKeyword().trim())) {
            queryWrapper.like(SQLConf.CAT_NAME, rankCategoryDto.getKeyword().trim());
        }
        if (StringUtils.isNotEmpty(rankCategoryDto.getOrderByAscColumn())) {
            // 将驼峰转换成下划线
            String column = StringUtils.underLine(new StringBuffer(rankCategoryDto.getOrderByAscColumn())).toString();
            queryWrapper.orderByAsc(column);
        } else if (StringUtils.isNotEmpty(rankCategoryDto.getOrderByDescColumn())) {
            // 将驼峰转换成下划线
            String column = StringUtils.underLine(new StringBuffer(rankCategoryDto.getOrderByDescColumn())).toString();
            queryWrapper.orderByDesc(column);
        } else {
            queryWrapper.orderByDesc(SQLConf.SORT);
        }
        Page<RankCategoryEntity> page = new Page<>();
        page.setCurrent(rankCategoryDto.getCurrentPage());
        page.setSize(rankCategoryDto.getPageSize());
        queryWrapper.eq(SQLConf.STATUS, StatusEnum.ENABLE);
        IPage<RankCategoryEntity> pageList = this.page(page, queryWrapper);
        return pageList;
    }

    @Override
    public List<RankCategoryEntity> getList() {
        QueryWrapper<RankCategoryEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(SysConf.STATUS, StatusEnum.ENABLE);
        queryWrapper.orderByDesc(SQLConf.SORT);
        List<RankCategoryEntity> blogSortList = this.list(queryWrapper);
        return blogSortList;
    }

    @Override
    public String addRankCategory(RankCategoryDto rankCategoryDto) {
        // 判断添加的分类是否存在
        QueryWrapper<RankCategoryEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(SQLConf.CAT_NAME, rankCategoryDto.getCatName());
        queryWrapper.eq(SQLConf.STATUS, StatusEnum.ENABLE);
        RankCategoryEntity tempSort = this.getOne(queryWrapper);
        if (tempSort != null) {
            return ResultUtil.errorWithMessage(MessageConf.ENTITY_EXIST);
        }
        RankCategoryEntity rankCategoryEntity = new RankCategoryEntity();
        // 使用Spring工具类提供的深拷贝进行实体类赋值
        BeanUtils.copyProperties(rankCategoryDto, rankCategoryEntity, SysConf.STATUS);
        rankCategoryEntity.insert();
        return ResultUtil.successWithMessage(MessageConf.INSERT_SUCCESS);
    }

    @Override
    public String editRankCategory(RankCategoryDto rankCategoryDto) {
        RankCategoryEntity rankCategoryEntity = this.getById(rankCategoryDto.getUid());
        /**
         * 判断需要编辑的博客分类是否存在
         */
        if (!rankCategoryEntity.getCatName().equals(rankCategoryDto.getCatName())) {
            QueryWrapper<RankCategoryEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq(SQLConf.CAT_NAME, rankCategoryDto.getCatName());
            queryWrapper.eq(SQLConf.STATUS, StatusEnum.ENABLE);
            RankCategoryEntity tempSort = this.getOne(queryWrapper);
            if (tempSort != null) {
                return ResultUtil.errorWithMessage(MessageConf.ENTITY_EXIST);
            }
        }
        // 使用Spring工具类提供的深拷贝进行实体类赋值
        BeanUtils.copyProperties(rankCategoryDto, rankCategoryEntity, SysConf.STATUS);
        rankCategoryEntity.updateById();
        // 删除和榜单相关的Redis缓存
        rankService.deleteRedisByRankCategory();
        return ResultUtil.successWithMessage(MessageConf.UPDATE_SUCCESS);
    }

    @Override
    public String deleteBatchRankCategory(List<RankCategoryDto> blogSortVoList) {
        if (blogSortVoList.size() <= 0) {
            return ResultUtil.errorWithMessage(MessageConf.PARAM_INCORRECT);
        }
        List<String> uids = new ArrayList<>();

        blogSortVoList.forEach(item -> {
            uids.add(item.getUid());
        });

        // 判断要删除的分类，是否有榜单
        QueryWrapper<RankEntity> rankQueryWrapper = new QueryWrapper<>();
        rankQueryWrapper.eq(SQLConf.STATUS, StatusEnum.ENABLE);
        rankQueryWrapper.in(SQLConf.CAT_UID, uids);
        Integer rankCount = rankService.count(rankQueryWrapper);
        if (rankCount > 0) {
            return ResultUtil.errorWithMessage(MessageConf.RANK_UNDER_THIS_CATEGORY);
        }
        Collection<RankCategoryEntity> blogSortList = this.listByIds(uids);
        blogSortList.forEach(item -> {
            item.setUpdateTime(new Date());
            item.setStatus(StatusEnum.DISABLED);
        });
        Boolean save = this.updateBatchById(blogSortList);
        if (save) {
            // 删除和博客相关的Redis缓存
            rankService.deleteRedisByRankCategory();
            return ResultUtil.successWithMessage(MessageConf.DELETE_SUCCESS);
        } else {
            return ResultUtil.errorWithMessage(MessageConf.DELETE_FAIL);
        }
    }

    @Override
    public String stickRankCategory(RankCategoryDto rankCategoryDto) {
        RankCategoryEntity blogSort = this.getById(rankCategoryDto.getUid());

        //查找出最大的那一个
        QueryWrapper<RankCategoryEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc(SQLConf.SORT);
        Page<RankCategoryEntity> page = new Page<>();
        page.setCurrent(0);
        page.setSize(1);
        IPage<RankCategoryEntity> pageList = this.page(page, queryWrapper);
        List<RankCategoryEntity> list = pageList.getRecords();
        RankCategoryEntity maxSort = list.get(0);

        if (StringUtils.isEmpty(maxSort.getUid())) {
            return ResultUtil.errorWithMessage(MessageConf.PARAM_INCORRECT);
        }
        if (maxSort.getUid().equals(blogSort.getUid())) {
            return ResultUtil.errorWithMessage(MessageConf.THIS_SORT_IS_TOP);
        }
        Integer sortCount = maxSort.getSort() + 1;
        blogSort.setSort(sortCount);
        blogSort.setUpdateTime(new Date());
        blogSort.updateById();
        return ResultUtil.successWithMessage(MessageConf.OPERATION_SUCCESS);
    }

    @Override
    public List<RankCategoryEntity> getHotRankCategory(Integer hotRankCategory) {
        QueryWrapper<RankCategoryEntity> queryWrapper = new QueryWrapper<>();
        Page<RankCategoryEntity> page = new Page<>();
        page.setCurrent(1);
        page.setSize(hotRankCategory);
        queryWrapper.eq(SQLConf.STATUS, StatusEnum.ENABLE);
        queryWrapper.orderByDesc(SQLConf.SORT);
        queryWrapper.orderByDesc(SQLConf.CLICK_COUNT);
        IPage<RankCategoryEntity> pageList = this.page(page, queryWrapper);
        return pageList.getRecords();
    }

    @Override
    public RankCategoryEntity getTopOne() {
        QueryWrapper<RankCategoryEntity> tagQueryWrapper = new QueryWrapper<>();
        tagQueryWrapper.eq(SQLConf.STATUS, StatusEnum.ENABLE);
        tagQueryWrapper.last(SysConf.LIMIT_ONE);
        tagQueryWrapper.orderByDesc(SQLConf.SORT);
        RankCategoryEntity rankCategoryEntity = this.getOne(tagQueryWrapper);
        return rankCategoryEntity;
    }

    /********************* 提供给 admin 的方法 endregion*********************/

    @Override
    public List<RankCategoryEntity> listWithTree() {
        //1、查出所有分类
        List<RankCategoryEntity> entities = baseMapper.selectList(null);

        //2、组装成父子的树形结构
        //2.1）、找到所有的一级分类
        List<RankCategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
                StringUtils.isEmpty(categoryEntity.getParentUid())
        ).map((menu) -> {
            menu.setChildren(getChildrens(menu, entities));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());


        return level1Menus;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO 1 检查当前的菜单是否被别的地方所引用
        //
        baseMapper.deleteBatchIds(asList);
    }

    /**
     * 找出catelogId的完整路径：即找出它的完整上级包括它本身
     * [父/子/孙]
     *
     * @param catelogUid
     * @return
     */
    @Override
    public String[] findCatelogPath(String catelogUid) {
        List<String> paths = new ArrayList<>();
        List<String> parentPath = findParentPath(catelogUid, paths);
        // 调换顺序，反转
        Collections.reverse(parentPath);
        return parentPath.toArray(new String[parentPath.size()]);
    }

    private List<String> findParentPath(String catelogUid,List<String> paths){
        // 收集当前节点id
        paths.add(catelogUid);
        RankCategoryEntity entity = this.getById(catelogUid);
        if(entity!=null && !StringUtils.isEmpty(entity.getParentUid())){
            findParentPath(entity.getParentUid(),paths);
        }
        return paths;
    }

    //递归查找所有菜单的子菜单
    private List<RankCategoryEntity> getChildrens(RankCategoryEntity root, List<RankCategoryEntity> all) {

        List<RankCategoryEntity> children = all.stream().filter(categoryEntity -> {
            return root.getUid().equals(categoryEntity.getParentUid());
        }).map(categoryEntity -> {
            //1、找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            //2、菜单的排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }

    /**
     * 查出所有的1级分类
     *
     * @return
     */
    @Cacheable(value = {"category"}, key = "#root.method.name", sync = true)
    @Override
    public List<RankCategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys.....");
        List<RankCategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<RankCategoryEntity>().eq("parent_uid", 0));
        return categoryEntities;
    }

    /**
     * 查询所有分类数据，并分级，然后封装成map格式
     *
     * @return
     */
    @Cacheable(value = {"category"}, key = "#root.methodName", sync = true)
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        System.out.println("查询了数据库.....");
        /**
         * 1. 将数据库的多次查询变为一次
         */
        List<RankCategoryEntity> selectList = baseMapper.selectList(null);
        // 查出所有的一级分类
        List<RankCategoryEntity> level1Categorys = listByParentId(selectList, null);
        //2、封装数据
        Map<String, List<Catelog2Vo>> categoryMap = level1Categorys.stream().collect(Collectors.toMap(k -> k.getUid(), v -> {
            //2.1、查询一级分类下的所有二级分类
            List<RankCategoryEntity> categoryEntities = listByParentId(selectList, v.getUid());
            //封装上面面的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getUid(), null, l2.getUid(), l2.getCatName());
                    //2.2、找当前二级分类的三级分类封装成vo
                    List<RankCategoryEntity> level3Catelog = listByParentId(selectList, l2.getUid());
                    if (level3Catelog != null) {
                        List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
                            // 封装成指定格式
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getUid(), l3.getUid(), l3.getCatName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(collect);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }


            return catelog2Vos;
        }));
        return categoryMap;
    }

    /**
     * 从 selectList 筛选出 parentId为指定值的分类数据
     * @param list
     * @param parentUid
     * @return
     */
    private List<RankCategoryEntity> listByParentId(List<RankCategoryEntity> list, String parentUid) {
        if(StringUtils.isEmpty(parentUid)){
            return list.stream().filter(item -> StringUtils.isEmpty(item.getParentUid())).collect(Collectors.toList());
        }
        List<RankCategoryEntity> collect = list.stream().filter(item -> parentUid.equals(item.getParentUid())).collect(Collectors.toList());
        return collect;
    }

    /**
     * 查询所有分类数据：优先查询缓存，缓存没有，再查询数据库
     * @return
     */
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson2() {
        //给缓存中放json字符串，拿出的json字符串，还用逆转为能用的对象类型；【序列化与反序列化】
        /**
         * 1、空结果缓存：解决缓存穿透
         * 2、设置过期时间（加随机值）：解决缓存雪崩
         * 3、加锁：解决缓存击穿
         */
        //1、加入缓存逻辑,缓存中存的数据是json字符串。
        //JSON跨语言，跨平台兼容。
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON)) {
            //2、缓存中没有,查询数据库
            //保证数据库查询完成以后，将数据放在redis中，这是一个原子操作。
            System.out.println("缓存不命中....将要查询数据库...");
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedissonLock();

            return catalogJsonFromDb;
        }

        System.out.println("缓存命中....直接返回....");
        //转为我们指定的对象。
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return result;
    }

    /**
     * 修改分类数据
     *
     * @param category
     */
    @Caching(evict = {
            @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
            @CacheEvict(value = "category",key = "'getCatalogJson'")
    })
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateCascade(RankCategoryEntity category) {
        this.updateById(category);
        // TODO 修改其他表中的分类名称
    }

    /**
     * 缓存里面的数据如何和数据库保持一致
     * 缓存数据一致性
     * 1）、双写模式
     * 2）、失效模式
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {


        //1、锁的名字。 锁的粒度，越细越快。
        //锁的粒度：具体缓存的是某个数据，11-号商品；  product-11-lock product-12-lock   product-lock
        RLock lock = redissonClient.getLock("CatalogJson-lock");
        lock.lock(30, TimeUnit.SECONDS);


        Map<String, List<Catelog2Vo>> dataFromDb;
        try {
            dataFromDb = getDataFromDb();
        } finally {
            lock.unlock();
        }

        return dataFromDb;


    }

    /**
     * 查询数据库获取分类数据
     * @return
     */
    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (!StringUtils.isEmpty(catalogJSON)) {
            //缓存不为null直接返回
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return result;
        }
        System.out.println("查询了数据库.....");

        List<RankCategoryEntity> selectList = baseMapper.selectList(null);


        List<RankCategoryEntity> level1Categorys = listByParentId(selectList, null);

        //2、封装数据
        Map<String, List<Catelog2Vo>> categoryMap = level1Categorys.stream().collect(Collectors.toMap(k -> k.getUid(), v -> {
            //1、每一个的一级分类，查到这个一级分类的二级分类
            List<RankCategoryEntity> categoryEntities = listByParentId(selectList, v.getUid());
            //2、封装上面面的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getUid(), null, l2.getUid(), l2.getCatName());
                    //1、找当前二级分类的三级分类封装成vo
                    List<RankCategoryEntity> level3Catelog = listByParentId(selectList, l2.getUid());
                    if (level3Catelog != null) {
                        List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
                            //2、封装成指定格式
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getUid(), l3.getUid(), l3.getCatName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(collect);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }


            return catelog2Vos;
        }));


        //3、查到的数据再放入缓存，将对象转为json放在缓存中
        String s = JSON.toJSONString(categoryMap);
        stringRedisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
        return categoryMap;
    }

}